package smallfrench.moteur.interp;

import java.awt.Window;
import java.util.Vector;

import smallfrench.moteur.biblio.RFonctionScript;
import smallfrench.moteur.interp.err.IntercepteurErreurs;
import smallfrench.moteur.interp.err.RException;

/**
 * Gestionnaire des différents threads d’exécution et des éventuelles erreurs.
 * @author antoine1023
 *
 */
public final class ProcessusScript extends EcoutableExcecution {

	/**
	 * La sortie standard
	 */
	public final ISortieConsole sortie;
	/**
	 * La sortie d’erreur
	 */
	public final ISortieConsole sortieErreurs;
	/**
	 * L’entrée standard
	 */
	public final IEntreeConsole entree;


	public final IntercepteurErreurs intercepteurErreurs;


	/**
	 * Le thread principal.
	 */
	private Tache tachePrincipale;

	/**
	 * Les autres threads.
	 */
	private Vector<Tache> taches;

	/**
	 * Le thread des évènements, notre EDT, "Event Dispatcher Thread".
	 */
	private TacheEvenements tacheEvenements;

	/**
	 * Les fenêtres AWT ouvertes et non "disposed"
	 */
	private Vector<Window> fenetres;

	//////////////////////////////////////////////////////////////////////////

	/**
	 * 
	 * @param entree
	 * @param sortie
	 * @param sortieErreurs
	 * @param intercepteurErreurs
	 * @param tachePrincipal
	 */
	public ProcessusScript(
			IEntreeConsole entree,
			ISortieConsole sortie,
			ISortieConsole sortieErreurs,
			IntercepteurErreurs intercepteurErreurs,
			Tache tachePrincipal) {

		intercepteurErreurs.peutIntercepter(Throwable.class);

		this.entree = entree;
		this.sortieErreurs = sortieErreurs;
		this.sortie = sortie;

		this.intercepteurErreurs = intercepteurErreurs;

		this.tachePrincipale = tachePrincipal;
		tachePrincipal.setProcessusScript(this);

		taches = new Vector<Tache>();

		fenetres = new Vector<Window>();

		tacheEvenements = new TacheEvenements(this);
		tacheEvenements.start();
	}

	//////////////////////////////////////////////////////////////////////////

	/**
	 * Appelé par {@link Tache} quand une erreur d’exécution s’est produite.
	 * Met fin à l’exécution.
	 * @param re
	 */
	void intercepterRException(RException re) {
		intercepteurErreurs.obtenir(RException.class).intercepter(re);
		tuer();
	}

	/**
	 * Appelé par {@link Tache} quand une erreur d’exécution s’est produite.
	 * Met fin à l’exécution.
	 * @param re
	 */
	public void intercepterRuntimeException(RuntimeException re) {
		intercepteurErreurs.obtenir(RuntimeException.class).intercepter(re);
		tuer();
	}

	//////////////////////////////////////////////////////////////////////////

	public Tache getTachePrincipale() {
		return tachePrincipale;
	}

	/**
	 * Met fin à toutes les tâches, ferme toutes les fenêtres. Ne signale rien aux
	 * écouteurs.
	 */
	private void tuer() {

		for (Window w : fenetres) {
			w.dispose();
		}

		for (Tache i : taches) {
			i.interrupt();
		}

		if (! tachePrincipale.estMort())
			tachePrincipale.interrupt();

		tacheEvenements.interrupt();
	}


	public void ajouterTache(Tache tache) {
		taches.add(tache);
		tache.setProcessusScript(this);
	}

	/**
	 * Cette méthode doit être appelée uniquement par {@link Tache}, juste avant
	 * qu’un thread meurt.
	 * Ne doit pas être appelé en cas d’erreur.
	 * @param tache
	 */
	public void enleverTache(Tache tache) {

		if (! tache.estMort())
			throw new IllegalStateException("Le thread " + tache +
					" n’est pas mort");

		if (tache != tachePrincipale) {
			taches.remove(tache);
		}

		signalerFinSiMort();
	}

	public void ajouterFenetre(Window fenetre) {
		fenetres.add(fenetre);
	}

	/**
	 * Quand une fenêtre n’existe plus (méthode
	 * <code>{@link Window}.dispose()</code>)
	 * Ne doit pas être appelé en cas d’erreur.
	 * @param fenetre
	 */
	public void retirerFenetre(Window fenetre) {
		fenetres.remove(fenetre);
		signalerFinSiMort();
	}

	public TacheEvenements getTacheEvenement() {
		return tacheEvenements;
	}

	public void ajouterEvenement(RFonctionScript f, RObjet[] arguments) {
		tacheEvenements.ajouterEvenement(f, arguments);
	}

	/**
	 * 
	 * @return <code>true</code> si l’exécution est terminée. Ne tient pas en
	 * compte de l’état du thread des évènements.
	 */
	private boolean estMort() {
		return fenetres.isEmpty() && tachePrincipale.estMort()
				&& taches.isEmpty();
	}

	/**
	 * Si l’exécution est terminée, le signale aux écouteurs.
	 * Ne doit pas être appelé si une exception s’est produite.
	 */
	private void signalerFinSiMort() {
		if (estMort() && ! tacheEvenements.evenementEstEnCours()) {
			signalerFin(this);
		}
	}

	/**
	 * Met fin à l’exécution de force, puis le signale aux écouteurs.
	 * Ne doit pas être appelé en cas d’erreur.
	 */
	public void tuerEtSignaler() {
		if (estMort() && ! tacheEvenements.isAlive())
			throw new IllegalStateException("L’exécution est déjà terminée");

		tuer();
		signalerFin(this);
	}

	/**
	 * @return Une référence au {@link ProcessusScript}, récupérée à partir du
	 * thread courant ou <code>null</code>.
	 */
	public static ProcessusScript getInstance() {
		Thread t = Thread.currentThread();
		if (t instanceof Tache)
			return ((Tache) t).getProcessusScript();
		else if (t instanceof TacheEvenements)
			return ((TacheEvenements) t).getProcessusScript();
		else
			return null;
	}

	@Override
	public String toString() {
		return "ProcessusScript";
	}

}
